2.1 变量

在数学概念中,变量(variable)表示没有固定值且可改变的数。但从计算机系统实现角度来看,变量是一段或多段用来存储数据的内存。

作为静态类型语言,Go变量总是有固定的数据类型,类型决定了变量内存的长度和存储格式。我们只能修改变量值,无法改变类型。

通过类型转换或指针操作,我们可用不同方式修改变量值,但这并不意味着改变了变量类型

因为内存分配发生在运行期,所以在编码阶段我们用一个易于阅读的名字来表示这段内存。实际上,编译后的机器码从不使用变量名,而是直接通过内存地址来访问目标数据。保存在符号表中的变量名等信息可被删除,或用于输出更详细的错误信息。

定义

关键字var用于定义变量,和C不同,类型被放在变量名后。另外,运行时内存分配操作会确保变量自动初始化为二进制零值(zero value),避免出现不可预测行为。如显式提供初始化值,可省略变量类型,由编译器推断。

var x int         // 自动初始化为0
var y=false         // 自动推断为bool类型

可一次定义多个变量,包括用不同初始值定义不同类型。

var x,y int          // 相同类型的多个变量 
var a,s=100, "abc"       // 不同类型初始化值

依照惯例,建议以组方式整理多行变量定义。

var( 
   x,y int
   a,s=100, "abc" 
)

简短模式

除var关键字外,还可使用更加简短的变量定义和初始化语法。

func main() { 
   x:=100
   a,s:=1, "abc" 
}

只是要注意,简短模式(short variable declaration)有些限制:

  • 定义变量,同时显式初始化。
  • 不能提供数据类型。
  • 只能用在函数内部。

对于粗心的新手,这可能会造成意外错误。比如原本打算修改全局变量,结果变成重新定义同名局部变量。

var x=100
  
func main() { 
   println(&x,x)       // 全局变量 
  
   x:= "abc"          // 重新定义和初始化同名局部变量 
   println(&x,x) 
}
 

输出:

0xae020     100// 对比内存地址,可以看出是两个不同的变量 
0xc820041f38     abc

简短定义在函数多返回值,以及if/for/switch等语句中定义局部变量非常方便。

简短模式并不总是重新定义变量,也可能是部分退化的赋值操作。

func main() { 
   x:=100
   println(&x) 
  
   x,y:=200, "abc"        // 注意:x退化为赋值操作,仅有y是变量定义 
  
   println(&x,x) 
   println(y) 
}
 

输出:

0xc820041f28
0xc820041f28 200       // 对比变量内存地址,可以确认x属于同一变量 
abc

退化赋值的前提条件是:最少有一个新变量被定义,且必须是同一作用域

func main() { 
   x:=100
   println(&x) 
  
   x:=200           // 错误:no new variables on left side of:= 
   println(&x,x) 
}
 
func main() { 
   x:=100
   println(&x,x) 
  
    { 
       x,y:=200,300  // 不同作用域,全部是新变量定义 
       println(&x,x,y) 
    } 
}

输出:

0xc820041f30     100
0xc820041f38     200 300

在处理函数错误返回值时,退化赋值允许我们重复使用err变量,这是相当有益的。

package main
  
import( 
    "log" 
    "os" 
) 
  
func main() { 
   f,err:=os.Open("/dev/random") 
    ... 
  
   buf:=make([]byte,1024) 
   n,err:=f.Read(buf) //err退化赋值,n新定义 
    ... 
} 

多变量赋值

在进行多变量赋值操作时,首先计算出所有右值,然后再依次完成赋值操作。

func main() { 
   x,y:=1,2
   x,y=y+3,x+2     // 先计算出右值y+3、x+2,然后再对x、y变量赋值 
  
   println(x,y) 
}
 

输出:

$go build&& ./test
  
5 3
  
$go tool objdump-s"main\.main"test
  
TEXT main.main(SB)test.go
   MOVQ$0x1,AX        // 先使用AX、CX寄存器完成表达式x+2、y+3操作 
   MOVQ$0x2,CX
   ADDQ$0x3,CX
   ADDQ$0x2,AX
   MOVQ CX,0x10(SP)     // 然后将计算结果分别写入x、y变量 
   MOVQ AX,0x8(SP) 
  
   CALL runtime.printlock(SB) // 依次printint(x),printint(y) 
   MOVQ 0x10(SP),BX
   MOVQ BX,0(SP) 
   CALL runtime.printint(SB) 
   CALL runtime.printsp(SB) 
  
   MOVQ 0x8(SP),BX
   MOVQ BX,0(SP) 
   CALL runtime.printint(SB) 
   CALL runtime.printnl(SB) 
   CALL runtime.printunlock(SB)

赋值操作,必须确保左右值类型相同。

未使用错误

编译器将未使用局部变量当作错误。不要觉得麻烦,这有助于培养良好的编码习惯

var x int         // 全局变量没问题 
  
func main() { 
   y:=10
}
 

输出:

$go build
  
./test.go:y declared and not used